/* Copyright (c) 2019 285424336@qq.com.
 * All rights reserved.
 * Author: 285424336
 * License: LGPLV3
 */
#include <glib.h>
#include <gio/gio.h>

#include <cstdlib>
#include <memory>

#ifdef G_OS_WIN32
#ifdef USE_VLD
#include <vld.h>
#endif
#endif

const gchar* g_address = "127.0.0.1";
constexpr guint16 g_port = 7201;
constexpr guint16 g_udp_port2 = 7202;
static GThread* g_thread = nullptr;
int g_semaphore = 0;
GMutex g_mutex;
GCond g_cond;

static void create_udp1();
static void create_thread();

/**
 * @brief main 程序入口函数
 * @param argc 启动参数个数
 * @param argv 启动参数数组
 * @return 程序返回值
 */
int main(int argc, char** argv) {
    (void)argc;
    (void)argv;
    std::shared_ptr<GMainLoop> main_loop(g_main_loop_new(nullptr, FALSE), [](GMainLoop* loop) {
        if (nullptr != loop) {
            g_main_loop_unref(loop);
        }
    });
    g_assert_nonnull(main_loop.get());
    g_mutex_init(&g_mutex);
    g_cond_init(&g_cond);
    create_thread();
    create_udp1();
    g_main_loop_run(main_loop.get());
    g_thread_join(g_thread);
    g_thread_unref(g_thread);
    g_cond_clear(&g_cond);
    g_mutex_clear(&g_mutex);
    return EXIT_SUCCESS;
}

static gboolean udp_read_source_func(GSocket* socket,
                                     GIOCondition io_condition,
                                     gpointer user_data) {
    socket;
    io_condition;
    GSocket* udp_socket = static_cast<GSocket*>(user_data);
    do {
        const char* name = g_source_get_name(g_main_current_source());
        if (nullptr != name) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "name = %s\n", name);
        }
        gssize available_bytes = g_socket_get_available_bytes(udp_socket);
        if (-1 == available_bytes) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "-1 == available_bytes\n");
            break;
        }
        if (0 == available_bytes) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "0 == available_bytes\n");
            break;
        }
        gchar* buffer = static_cast<gchar*>(g_malloc0(available_bytes));
        g_assert_nonnull(buffer);
        GError* error = nullptr;
        gssize size = g_socket_receive(udp_socket,
                                       buffer,
                                       available_bytes,
                                       nullptr,
                                       &error);
        if (nullptr != error) {
            g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
            g_error_free(error);
            error = nullptr;
        }
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "g_input_stream_read = %d\n", size);
        g_free(buffer);
    } while (0);
    return G_SOURCE_CONTINUE;
}

static gpointer thread_func(gpointer data) {
    (void)data;
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "thread enter\n");
    std::shared_ptr<GMainContext> thread_main_context(g_main_context_new(), [](GMainContext* main_context) {
        if (nullptr != main_context) {
            g_main_context_unref(main_context);
        }
    });
    std::shared_ptr<GMainLoop> thread_main_loop(g_main_loop_new(thread_main_context.get(), FALSE), [](GMainLoop* loop) {
        if (nullptr != loop) {
            g_main_loop_unref(loop);
        }
    });
    GError* error = nullptr;
    GSocket* udp2_receive_socket = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_DEFAULT, &error);
    if (nullptr != error) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
        g_error_free(error);
        error = nullptr;
        g_assert_nonnull(error);
    }
    GSocketAddress* udp2_receive_address = g_inet_socket_address_new_from_string(g_address, g_udp_port2);
    g_assert_nonnull(udp2_receive_address);
    g_socket_bind(udp2_receive_socket,
                  udp2_receive_address,
                  TRUE,
                  &error);
    if (nullptr != error) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
        g_error_free(error);
        error = nullptr;
        g_assert_nonnull(error);
    }
    g_socket_set_timeout(udp2_receive_socket, 5);
    GSource* udp2_receive_source = g_socket_create_source(udp2_receive_socket,
                                                          G_IO_IN,
                                                          nullptr);
    g_assert_nonnull(udp2_receive_source);
    g_source_set_name(udp2_receive_source, "udp2_receive_source");
    g_source_set_callback(udp2_receive_source, static_cast<GSourceFunc>(udp_read_source_func), udp2_receive_socket, nullptr);
    g_source_attach(udp2_receive_source, g_main_context_get_thread_default());

    g_mutex_lock(&g_mutex);
    if (++g_semaphore <= 0) {
        g_cond_signal(&g_cond);
    }
    g_mutex_unlock(&g_mutex);

    GSocket* udp2_send_socket = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_DEFAULT, &error);
    if (nullptr != error) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
        g_error_free(error);
        error = nullptr;
        g_assert_nonnull(error);
    }
    GSocketAddress* udp2_send_address = g_inet_socket_address_new_from_string(g_address, g_port);
    g_assert_nonnull(udp2_send_address);
    g_socket_set_timeout(udp2_send_socket, 5);
    const gchar* buffer = "hello";
    gssize size = g_socket_send_to(udp2_send_socket,
                                   udp2_send_address,
                                   buffer,
                                   strlen(buffer),
                                   nullptr,
                                   &error);
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "size = %d\n", size);
    if (nullptr != error) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "!boolean && (nullptr != error):%s\n", error->message);
        g_error_free(error);
        error = nullptr;
        g_assert_nonnull(error);
    }
    g_main_loop_run(thread_main_loop.get());
    return nullptr;
}

static void create_udp1() {
    GError* error = nullptr;
    GSocket* udp1_receive_socket = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_DEFAULT, &error);
    if (nullptr != error) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
        g_error_free(error);
        error = nullptr;
        g_assert_nonnull(error);
    }
    GSocketAddress* udp1_receive_address = g_inet_socket_address_new_from_string(g_address, g_port);
    g_assert_nonnull(udp1_receive_address);
    g_socket_bind(udp1_receive_socket,
                  udp1_receive_address,
                  TRUE,
                  &error);
    if (nullptr != error) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
        g_error_free(error);
        error = nullptr;
        g_assert_nonnull(error);
    }
    g_socket_set_timeout(udp1_receive_socket, 5);
    GSource* udp1_receive_source = g_socket_create_source(udp1_receive_socket,
                                                          G_IO_IN,
                                                          nullptr);
    g_assert_nonnull(udp1_receive_source);
    g_source_set_name(udp1_receive_source, "udp1_receive_source");
    g_source_set_callback(udp1_receive_source, static_cast<GSourceFunc>(udp_read_source_func), udp1_receive_socket, nullptr);
    g_source_attach(udp1_receive_source, g_main_context_get_thread_default());

    g_mutex_lock(&g_mutex);
    if (--g_semaphore < 0) {
        g_cond_wait(&g_cond, &g_mutex);
    }
    g_mutex_unlock(&g_mutex);

    GSocket* udp1_send_socket = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_DEFAULT, &error);
    if (nullptr != error) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "nullptr != error:%s\n", error->message);
        g_error_free(error);
        error = nullptr;
        g_assert_nonnull(error);
    }
    GSocketAddress* udp1_send_address = g_inet_socket_address_new_from_string(g_address, g_udp_port2);
    g_assert_nonnull(udp1_send_address);
    g_socket_set_timeout(udp1_send_socket, 5);
    const gchar* buffer = "glib";
    gssize size = g_socket_send_to(udp1_send_socket,
                                   udp1_send_address,
                                   buffer,
                                   strlen(buffer),
                                   nullptr,
                                   &error);
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "size = %d\n", size);
    if (nullptr != error) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "!boolean && (nullptr != error):%s\n", error->message);
        g_error_free(error);
        error = nullptr;
        g_assert_nonnull(error);
    }
}

static void create_thread() {
    g_thread = g_thread_new("thread", thread_func, nullptr);
    g_assert_nonnull(g_thread);
}
